package cn.org.itool.ws.framework.netty.channel;

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import cn.org.itool.ws.framework.event.CloseEvent;
import cn.org.itool.ws.framework.netty.auth.Principal;
import cn.org.itool.ws.framework.netty.message.*;
import cn.org.itool.ws.framework.protocol.pomelo.Protocol;
import cn.org.itool.ws.framework.protocol.service.ProtocolService;
import cn.org.itool.ws.framework.thread.ServiceThreadPool;
import cn.org.itool.ws.framework.utils.ChannelUtils;
import cn.org.itool.ws.framework.ws.WsServer;
import cn.org.itool.ws.framework.ws.command.CommandHandlerMethodRegistry;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.log4j.Log4j2;
import org.springframework.core.GenericTypeResolver;
import org.springframework.lang.Nullable;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.RejectedExecutionException;

@Log4j2
@ChannelHandler.Sharable
public class BizHandler extends SimpleChannelInboundHandler<BizWsPackage> {
    protected final ProtocolService PROTOCOL_SERVICE = WsServer.PROTOCOL_SERVICE;

    protected final CommandHandlerMethodRegistry HANDLER_METHOD_REGISTRY = WsServer.METHOD_REGISTRY;

    protected final ServiceThreadPool SERVICE_THREAD_POOL = ServiceThreadPool.getInstance(WsServer.NETTY_PROPERTIES);

    protected final ObjectMapper OBJECT_MAPPER = WsServer.OBJECT_MAPPER;


    public BizHandler() {
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        log.info("channelInactive:{}", ctx.channel().id().asLongText());
        Optional<Principal> optional = ChannelUtils.getPrincipalOpt(ctx.channel());
        optional.ifPresent(e -> {
            try{
                String wsId = ctx.channel().id().asLongText();
                ctx.fireUserEventTriggered(new CloseEvent(e, wsId));
            }catch (Exception ex){
                //none instance
            }
        });

        ctx.fireChannelInactive();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, BizWsPackage bizWsPackage){
        // 客户端到服务器的握手请求以及服务器到客户端的握手响应
        if(bizWsPackage.getType() == BizWsPackage.HAND_SHAKE) {
            WsHandShake wsHandShake  = new WsHandShake(bizWsPackage.getBody());

            // 响应握手包
            ctx.channel().writeAndFlush(wsHandShake);
        }
        // 客户端到服务器的握手ack
        else if (bizWsPackage.getType() == BizWsPackage.HAND_SHAKE_AC) {
            // 客户端到服务器的握手ack
            ctx.channel().writeAndFlush(new WsHandShakeAck());
        }

        // 心跳包
        else if(bizWsPackage.getType() == BizWsPackage.HEART_BEAT) {
            // 服务器的心跳包
            ctx.channel().writeAndFlush(new WsHeartbeat());
        }

        // 数据包
        else if(bizWsPackage.getType() == BizWsPackage.DATA) {
            BizWsMessage bizWsMessage = PROTOCOL_SERVICE.decodeMessage(bizWsPackage.getBody());

            try {
                this.invoke(ctx, bizWsMessage);
            }catch (Exception e) {
                log.error("e:", e);
            }

        }
    }


    public void invoke(ChannelHandlerContext ctx, BizWsMessage websocketMessage){
        log.info("message decode");

        String route = String.valueOf(websocketMessage.getRoute());

        Method method = HANDLER_METHOD_REGISTRY.getMethod(route);
        if (Objects.isNull(method)) {
            log.error("route error {}", route);
            return;
        }
        Channel channel = ctx.channel();
        final Integer id = websocketMessage.getId();
        Runnable task = () -> {
            try {
                Object serviceInstance = HANDLER_METHOD_REGISTRY.getServiceInstance(route);
                Parameter[] methodArgArr = HANDLER_METHOD_REGISTRY.getMethodArg(route);
                Object[] params = null;
                if(!Objects.isNull(methodArgArr)){
                    params = new Object[methodArgArr.length];
                    for (int i = 0; i < methodArgArr.length; i++) {
                        Parameter methodArg = methodArgArr[i];
                        if(methodArg.getType() != Channel.class) {
                            Object object;
                            String bodyStr = String.valueOf(websocketMessage.getBody());

                            if(!JSON.isValid(bodyStr)) {
                                object = bodyStr;
                            } else {
                                object = OBJECT_MAPPER.convertValue(OBJECT_MAPPER.readTree(bodyStr), getJavaType(methodArg.getParameterizedType(), null));
                            }
                            params[i] = object;
                        } else {
                            params[i] = channel;
                        }
                    }
                }

                Object result = method.invoke(serviceInstance, params);
                if (Protocol.Message.TYPE_REQUEST == websocketMessage.getType()) {
                    BizWsMessage resp = new BizWsMessage();
                    resp.setId(id);
                    resp.setType(Protocol.Message.TYPE_RESPONSE);
                    resp.setBody(result);

                    ctx.channel().writeAndFlush(resp);
                }
            } catch (Exception ex) {
                log.error("调用处理方法异常", ex);
            }
        };

        try {
            SERVICE_THREAD_POOL.getExecutor().submit(task);
        } catch (RejectedExecutionException ex) {
            log.error("业务线程池工作队列已经满", ex);
        }
    }

    private JavaType getJavaType(Type type, @Nullable Class<?> contextClass) {
        TypeFactory typeFactory = OBJECT_MAPPER.getTypeFactory();
        return typeFactory.constructType(GenericTypeResolver.resolveType(type, contextClass));
    }
}
